<template>
  <div>
    <!--switch开关
            点击开关就会更改 v-model双向绑定的值， dragSwitch自动在false和true之间变换，不需要显式地改变dragSwitch的值。要在data中定义
            然后再把dragSwitch 绑定到其他组件的开关id上
            active-text和inactive-text为开关左右的字段
            然后绑定一个状态切换时的回调函数，用于恢复相关属性的默认值-->
    <el-switch
      v-model="dragSwitch"
      active-text="开启拖拽"
      inactive-text="关闭拖拽"
      @change="dragSwitchChange"
    >
    </el-switch>
    <!--保存按钮
             :disabled绑定!dragSwitch，允许拖拽时允许点击，不允许拖拽时也不准点击 -->
    <el-button v-if="dragSwitch" type="text" size="mini" @click="batchSave"
      >保存拖拽结果
    </el-button>
    <!--批量删除-->
    <el-button type="danger" size="mini" @click="batchDelete"
      >批量删除</el-button
    >
    <!--首先在Template中导入使用的现成组件，这里就是使用el-tree
            :data="menus"  声明将下方的用户注入对象数据集合（data）中的menus属性，映射到el-tree的data属性。
            :props="defaultProps"  ，根据:props中的字段设置，从:data中取值来作为el-tree的显示内容。好好理解。
            :expand-on-click-node="false"    设置菜单默认不展开，只有点击下拉箭头时才展开
            :default-expanded-keys="expandedKey"，配置默认展开的菜单catId，由注入对象的数据集合（data）中的expandedKey映射过来
            :draggable="dragSwitch"，是否允许拖拽，绑定switch开关的值
            :allow-drop="allowDrop"，用于判断当前的拖拽是否允许放置，由一个函数的返回值来决定是否允许放置
            show-checkbox  展示复选框，不写就是不展示
            node-key="catId"  设置每个结点的唯一标识名称，这里不是绑定了，这里就是直接赋值，唯一标识的名称是catId
            @node-click="handleNodeClick"  监听单击事件，将下方的用户注入对象的方法集合（methods）中的handleNodeClick()方法，绑定到el-tree的node-click属性
            @node-drop="handleDrop"监听拖拽时间，绑定handleDrop()方法
            在下方解释这几个属性的含义-->
    <el-tree
      :data="menus"
      :props="defaultProps"
      :default-expanded-keys="expandedKey"
      :expand-on-click-node="false"
      :draggable="dragSwitch"
      :allow-drop="allowDrop"
      show-checkbox
      node-key="catId"
      ref="menuTree"
      @node-click="handleNodeClick"
      @node-drop="handleDrop"
    >
      <!--上面只是定义这个el-tree，无法显示。下面开始设置显示内容-->
      <!--同时el-tree中使用slot-scope机制， slot-scope="{ node, data }"是该组件的固定语法，node为当前结点信息，包含信息有限。data为当前结点的Entity数据，包含pojo全部信息。
              <span>{{ node.label }}</span>为每一个tree设置显示内容。<span>就是常规js语法。显示之后为每个label后都添加了两个按钮，
              即"添加子菜单"和"删除"按钮，并对应了两个方法append(data)和remove(node,data)，显然这两个方法也得自己写到methods:{}中
              使用v-if来设置按钮的显示条件：添加按钮只在当前结点信息（data）的catLevel <= 2时显示，删除按钮只在当前结点信息（data）的children.length==0时显示
              而"修改菜单"按钮在每个菜单后面都出现-->
      <!--注意单击事件的写法，要用()=>来指定-->
      <span class="custom-tree-node" slot-scope="{ node, data }">
        <span>{{ node.label }}</span>
        <span>
          <el-button
            v-if="data.catLevel <= 2"
            type="text"
            size="mini"
            @click="() => append(data)"
            >添加子菜单</el-button
          >
          <el-button
            v-if="node.childNodes.length == 0"
            type="text"
            size="mini"
            @click="() => remove(node, data)"
            >删除菜单</el-button
          >
          <el-button type="text" size="mini" @click="() => edit(data)"
            >修改菜单</el-button
          >
        </span>
      </span>
    </el-tree>

    <!--添加el-dialog组件-----添加子菜单对话框
            通过属性 :visible.sync 来决定何时拉起对话框。注意，:visible.sync的值是标识对话框的唯一标识位，要在注入对象的数据集合data中定义
            :model属性绑定一个元素，也就是这个对话框最后要提交的元素，这里就绑定 category 好了，记得在注入对象的数据集合data中定义这个元素
            v-model 执行一个双向绑定
            为'确定'按钮绑定单击方法-->
    <el-dialog
      title="添加子菜单"
      :visible.sync="dialogVisibleAppend"
      width="30%"
    >
      <el-form :model="category">
        <el-form-item label="菜单名称">
          <!--执行一个双向绑定，绑定 :model属性（即category）中的某一个属性，这里就定义为name即可，随之在注入对象的category元素中也要加入name属性。
                  所谓双向绑定，就是改动这两个中的哪一个，另一个都会保持同步
                  label是输入框的框名-->
          <el-input v-model="category.name" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item label="图标">
          <!--执行一个双向绑定-->
          <el-input v-model="category.icon" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item label="计量单位">
          <!--执行一个双向绑定-->
          <el-input
            v-model="category.productUnit"
            autocomplete="off"
          ></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisibleAppend = false">取 消</el-button>
        <!--确定按钮的单击方法，要在method集合中定义
               因为单击事件没有输入参数，所以直接写方法名也可以，写法是:@click="addCategory"-->
        <el-button type="primary" @click="()=>addCategory()">确 定</el-button>
      </span>
    </el-dialog>

    <!--添加el-dialog组件----修改菜单对话框
            注意，:visible.sync的值是dialogVisibleEdit，用于和增加对话框区分
            :model属性绑定一个元素，也就是这个对话框最后要提交的元素，依然是category(定义的一个全局变量)
            v-model 执行一个双向绑定，两者互相同步。不过由于一旦跳出这个对话框会执行clear，所以实际上只有在对话框内，修改数据会同步到category上。
            为'确定'按钮绑定单击方法-->
    <el-dialog title="修改菜单" :visible.sync="dialogVisibleEdit" width="30%">
      <el-form :model="category">
        <el-form-item label="菜单名称">
          <!--执行一个双向绑定，绑定 :model属性（即category）中的某一个属性，这里就定义为name即可，随之在注入对象的category元素中也要加入name属性。
                  该属性将作为输入框的框名-->
          <el-input v-model="category.name" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item label="图标">
          <!--执行一个双向绑定-->
          <el-input v-model="category.icon" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item label="计量单位">
          <!--执行一个双向绑定-->
          <el-input
            v-model="category.productUnit"
            autocomplete="off"
          ></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisibleEdit = false">取 消</el-button>
        <!--确定按钮的单击方法，要在method集合中定义-->
        <el-button type="primary" @click="editCategory">确 定</el-button>
      </span>
    </el-dialog>
  </div>
</template>

<script>
//  这里可以导入其他文件（比如组件、工具js、第三方插件js、json文件、图片文件等）
//  例如：import 组件名称  from '组件路径';

export default {
  // 在export default{}编写主要内容。包括定义全局变量(全局变量才能被上面定义的组件使用)、编写函数
  //  import 引入的组件需要注入到对象中才可以使用
  components: {},
  props: {},

  // 用户注入对象的数据集合，或者理解成全局变量更好。必须在data(){中进行定义}，定义后的数据可以被上方的组件或下方的函数使用。
  data() {
    return {
      // 这里menus会被映射到el-tree的data属性，menus中的数据应该从后端获取。
      // 在新建这个vue时，完全可以令menus为空
      menus: [],
      // 这里defaultProps会被映射到el-tree的props属性
      // el-tree会根据defaultProps设置的规则从menus中获取数据来作为el-tree的显示
      defaultProps: {
        // 对应的后端接口返回的是 CategoryEntity类，这个变量中有一个（我们定义的，名字随便取的）存放其子菜单的变量：children
        // 而el-tree的属性参数（不能更改的）children用于声明子菜单变量名。因此这里就是  children: "children"
        // 意思就是menus中的"children"字段的值将作为el-tree的children
        children: "children",
        // 对应的后端接口返回的是 CategoryEntity类，这个变量中有一个（自动根据数据表头生成的，一般不做修改的）存放当前菜单名称的变量：name
        // 而el-tree的属性参数（不能更改的）label用于声明菜单名称变量名。因此这里就是  label: "name"
        // 意思就是menus中的"name"字段的值将作为el-tree的label
        label: "name",
      },
      // 动态绑定switch开关值，决定是否允许拖拽
      dragSwitch: false,
      expandedKey: [],
      // 映射到"增加子菜单"dialog的 visible.sync 属性，如果为false则默认对话框不展开，否则默认展开
      dialogVisibleAppend: false,
      // 映射到"修改菜单"dialog的 visible.sync 属性
      dialogVisibleEdit: false,
      // 映射到"增加子菜单"\"修改菜单"dialog的model属性，即向数据库提交的数据。
      // 不要忘记根据后端中该对象的Entity实例中的字段，对这里进行所有字段的定义（最好和Entity的字段保持同名，这样提交式最简单）
      // name属性用于双向绑定，接收对话框的输入值，其他值都具有默认值。
      // 尤其注意catId的默认值为null，因为他是主键，当category向后端提交时，如果catId为空，后端会自动递增生成catId（这正是我们想要的）
      category: {
        catId: null,
        name: "",
        parentCid: 0,
        catLevel: 0,
        showStatus: 1,
        sort: 0,
        icon: null,
        productUnit: null,
      },
      // 用于保存当前结点的相对深度
      maxDepth: 0,
      // 一个列表，将来用来存放category，用于批量修改时。注意放进去的category未必每个字段都有非空值
      updateNodes: [],
    };
  },

  // 方法集合，名称必须是methods:{} 包装所有的方法
  methods: {
    // 这个点击函数，仅仅是在控制台打印一次 被点击的菜单自身的Entity信息 以及这个结点的信息
    // 注意注意注意!!!!!!!!!!!!!!这个data不是上面的注入对象的数据集合，而是这个结点对应的数据库Entity信息
    // Node则是结点信息。两者的关系是：Node.data = data
    handleNodeClick(data, Node) {
      console.log("Entity:", data, "结点:", Node);
    },

    // 判断本次拖拽是否允许放置
    // draggingNode--被拖动的分类。  dropNode--将要被放置的分类。  type--相对位置，有forward、behind、inner。只有inner需要考虑深度问题，排序不需要考虑深度
    allowDrop(draggingNode, dropNode, type) {
      // 首先判断draggingNode相对深度
      // 每次拖拽前都清空相对深度
      this.maxDepth = 0;
      this.countMaxDepth(draggingNode, draggingNode.data.catLevel);
      if (type == "inner") {
        return this.maxDepth + dropNode.data.catLevel <= 3;
      } else {
        return dropNode.data.catLevel + this.maxDepth <= 4;
      }
    },

    // 求结点的相对深度，而非绝对深度
    // Node当前结点，当前结点的层级
    countMaxDepth(Node, catLevel) {
      if (Node.childNodes.length == 0) {
        this.maxDepth = Math.max(
          this.maxDepth,
          Node.data.catLevel - catLevel + 1
        );
        return;
      }
      for (let i = 0; i < Node.childNodes.length; i++) {
        this.countMaxDepth(Node.childNodes[i], catLevel);
      }
    },

    // 监听拖拽成功事件，获取拖拽信息
    handleDrop(draggingNode, dropNode, dropType, ev) {
      let parentCid = 0; // 父结点id
      let catLevel = 0; // 本体层级
      let siblings = null; // 同级结点个数
      // 首先修改本体parentCid、catLevel的值，并获取同级子结点列表，准备排序
      if (dropType == "inner") {
        parentCid = dropNode.data.catId;
        catLevel = dropNode.level + 1; // 使用结点的动态level
        siblings = dropNode.childNodes;
      } else {
        parentCid = dropNode.level == 1 ? 0 : dropNode.parent.data.catId; // 要考虑顶层结点的特殊情况，使用结点的动态父结点（中的data.catId）
        catLevel = dropNode.level; // 使用结点的动态level
        siblings = dropNode.parent.childNodes;
      }
      // 排序+修改子结点的catLevel，同时也是封装要向后端提交的数据包。
      // 就是按照刚刚拖拽完成网页中看到的结点顺序，给每一个同级结点修改sort值
      // 因此列表updateNodes中存放多个category对象，只不过这些category对象只具有{catId值和sort值}
      // 只有当修改的结点是draggingNode时，category对象要把{catId、sort、parentCid、catLevel}以及draggingNode的全部子结点的{catId、atLevel}都放进去
      for (let i = 0; i < siblings.length; i++) {
        if (siblings[i].data.catId == draggingNode.data.catId) {
          // 如果本体层级发生了变化，就修改全部子结点层级，否则不修改
          // 调用递归方法，向下寻找自身的子结点
          if (catLevel != draggingNode.level) {
            this.updateChildNodeLevel(draggingNode, catLevel);
          }
          // 无论本体层级变不变，本体结点都是要修改的对象
          this.updateNodes.push({
            catId: siblings[i].data.catId,
            parentCid: parentCid,
            catLevel: catLevel,
            sort: i,
          });
        } else {
          // 和本体同级的菜单只修改排序值
          this.updateNodes.push({ catId: siblings[i].data.catId, sort: i });
        }
      }
      // 只要没有切换switch按钮，就一直向updateNodes中put数据
      console.log("待修改结点列表...", this.updateNodes);
    },

    // 在handleDrop()中提供的递归子结点修改子结点层级的递归方法
    // 输入参数：当前结点、当前结点的新层级
    updateChildNodeLevel(Node, catLevel) {
      if (Node.childNodes.length == 0) {
        return;
      }
      for (let i = 0; i < Node.childNodes.length; i++) {
        // 遍历子结点的子结点，此时当前结点就是子结点，它的catLevel就是父结点catLevel+1
        this.updateChildNodeLevel(Node.childNodes[i], catLevel + 1);
        // 每次递归退出时，封装进{子结点的catId，子结点的catLevel=父结点catLevel+1}
        this.updateNodes.push({
          catId: Node.childNodes[i].data.catId,
          catLevel: catLevel + 1,
        });
      }
    },

    batchSave() {
      // 提交全部拖拽的保存请求
      this.$http({
        url: this.$http.adornUrl("/gulimallproduct/category/update/sort"),
        method: "post",
        // 将封装好的结点列表发送出去
        data: this.$http.adornData(this.updateNodes, false),
      }).then(({ data }) => {
        this.$message({
          message: "拖拽修改成功",
          type: "success",
        });
        // 刷新并展开拖拽前后的菜单
        this.getMenus();
        // 拖拽前就是draggingNode.catId，拖拽后就是parentCid
      });
      // 清空updateNodes
      this.updateNodes = [];
    },

    // 拖拽开关切换状态时，清空提交属性
    dragSwitchChange() {
      this.updateNodes = [];
      this.getMenus();
    },

    // 批量删除
    batchDelete() {
      let checkedNodes = this.$refs.menuTree.getCheckedNodes();
      let deleteNodes = [];
      // 把返回的Entity数组抽取成catId数组
      for (let i = 0; i < checkedNodes.length; i++) {
        deleteNodes.push(checkedNodes[i].catId);
      }
      this.$confirm(
        `此操作将删除被选中的菜单及其全部子菜单，是否删除?`,
        "提示",
        {
          confirmButtonText: "确定",
          cancelButtonText: "取消",
          type: "warning",
        }
      )
        .then(() =>
          this.$http({
            url: this.$http.adornUrl("/gulimallproduct/category/delete"),
            method: "post",
            // 如果只有一个变量要发送，()内既可以直接向下面这样写，也可以写 
            // (deleteNodes:deleteNodes,fasle)前者是json中的字段名称，后者是这个字段的值
            // false为是否开启默认数据，默认是true，为true时，如果字段的值为空，会赋默认值进行传递，为false则保持null
            data: this.$http.adornData(deleteNodes, false),
          }).then(({ data }) => {
            this.$message({
              message: "批量删除成功",
              type: "success",
            });
            this.getMenus();
          })
        )
        .catch(() => {
          this.getMenus();
        });
    },

    // !!!关键： 在注入对象的methods中编写一个方法从后端获取数据，用于刷新页面
    // 这个方法可以直接从脚手架中已有的方法复制来修改：
    getMenus() {
      // this.$http代表当前对象访问的http属性，关于这个方法的详细信息可以去看src/utils/httpRequest.js
      // this.http(括号内书写http属性，如果有多个属性，则使用{大括号括起来})
      this.$http({
        // 属性url:this.$http.adornUrl("编写后端Controller接口的全路径")
        // 前端会向  $http.adornUrl/括号内容  发起请求。$http.adornUrl是前端项目的默认路径，之前已经设置为网关路径，具体在index.js中配置
        // 所以下面这个路径实际上就是在向 http://localhost:8888/api/gulimallproduct/category/list/tree 发送请求
        url: this.$http.adornUrl("/gulimallproduct/category/list/tree"),
        // 属性method: "声明访问后端接口是get还是post，当然这里和Controller保持一致"
        method: "get",
        /*
        // 属性params:this.$http.adornParams书写访问后端地址时的参数信息，如果是get方法，他们会以   ?$参数名=xxx   的形式附加在请求地址后面
        // params:this.$http.adornParams(括号内书写访问附加参数，如果多个参数，使用{大括号括起来})
        // 在本例中因为没有访问参数，所以下面的内容先注释掉
        params: this.$http.adornParams({
          // 用 page: this.pageIndex:  声明访问参数
          // page就是会附加在访问地址？后的内容
          // 如果之前在  data(){中集合了数据}，  则这里就要使用   this.data中数据的名称   来获取，否则直接给值就行
          page: this.pageIndex,
        }),
        */
        //  访问成功以后要做什么？
        //  注意，这里的data只能使用data！！！！不能使用  this.data
        //  因为vscode内部设定，后端返回的json文件自动以data命名，所以这里的{data}其实是在接收后端返回的json数据
        //  如果我们在控制台查看，我们发现这个名为data的json数据包中，存放了我们当初在Controller中定义返回值时，放入的
        //  return R.ok().put("allcategory", categoryEntities);  这个键值对
        // 也就是说这个json包中，名为 allcategory 的键下，存放着所有的菜单信息。
        //  所以把这个allcategory拿出来就是了呗
      }).then(({ data }) => {
        console.log("成功获取到菜单数据...", data.allcategory);
        // 把取出的菜单信息绑定到最开始定义的全局变狼 this.menus中去
        this.menus = data.allcategory;
      });
    },

    // el-tree中两个按钮对应的方法
    // append方法
    append(data) {
      // 调用复原方法恢复category
      this.clear();
      console.log("向当前菜单添加新菜单...", data);
      // 由于我们希望在点击append时展开对话框，因此在这里将dialogVisible置为true
      this.dialogVisibleAppend = true;
      // 同时自动计算出category其他字段的值:
      // 当前菜单的catId作为新菜单的父菜单Id
      this.category.parentCid = data.catId;
      // 当前菜单的catLevel + 1作为新菜单的catLevel。（*1是为了将字符串转换为数字）
      this.category.catLevel = data.catLevel * 1 + 1;
    },

    // 与提交对话框的确定按钮绑定，该方法才真正执行向数据库提交的工作
    addCategory() {
      console.log("提交数据...", this.category);
      // 向后端对应url发送post请求
      this.$http({
        url: this.$http.adornUrl("/gulimallproduct/category/save"),
        method: "post",
        data: this.$http.adornData(this.category, false),
      }).then(({ data }) => {
        // 提交成功同样发出提示信息
        this.$message({
          message: "添加成功",
          type: "success",
        });
        // console.log("添加新菜单...", data);
        // 关闭对话框并刷新页面
        this.dialogVisibleAppend = false;
        this.getMenus();
        // 设置el-tree的expandedKey属性，来默认展开刚刚添加的菜单的父菜单
        // 这里自然就是直接使用新添加的category对象的父菜单id即可
        this.expandedKey = [this.category.parentCid];
      });
    },

    // remove方法
    // 由于这个单独删除用的后端接口方法是一个批量删除方法，后端接收long[]数据，所以这里还需要包装一下
    // 输入参数node，由el-tree根据树形结构自行生成，data是刚刚从数据库中查询的数据
    remove(node, data) {
      // 在删除前弹窗进行提示
      // 使用差值表达式来在弹窗中显示具体信息
      this.$confirm(`此操作将删除【${data.name}】菜单，是否删除?`, "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      })
        // 点击确定执行：
        .then(() => {
          // 把这个单独的结点id包装成long[]
          var ids = [data.catId];
          this.$http({
            // remove方法的请求地址
            url: this.$http.adornUrl("/gulimallproduct/category/delete"),
            // 这是一个post请求
            method: "post",
            // 执行post请求时提交的RequestBody内容
            data: this.$http.adornData(ids, false),
            // 完成post请求后执行：
          }).then(({ data }) => {
            // elementUI的消息组件
            this.$message({
              message: "删除成功",
              type: "success",
            });
            // console.log("删除结点...", data);
            // 刷新，更新页面
            this.getMenus();
            // 设置el-tree的expandedKey属性，来默认展开刚刚删除的菜单的父菜单
            // 这里必须从node中获取父菜单的信息，node.parent.data.catId
            // 而不能使用data.parentCid，因为这个信息是丢失了的
            this.expandedKey = [node.parent.data.catId];
          });
        })
        //点击取消执行（没有操作就保留空，但是不能删除catch处理）：
        .catch(() => {
          // elementUI的消息组件
          this.$message("取消操作");
        });
    },

    // 修改菜单按钮的点击方法
    // 由于没有开启异步，所以edit()方法一定会在执行完之后才拉起编辑框
    // 因此在edit()方法中，已经将最新的菜单数据赋值给了全局变量category，这样在拉起编辑框后才能实现自动填充
    edit(data) {
      // 调用复原方法恢复category
      this.clear();
      this.dialogVisibleEdit = true;
      // 发送一次httpget请求，获取最新的数据
      this.$http({
        // 用插值表达式来获取catId
        url: this.$http.adornUrl(
          `/gulimallproduct/category/info/${data.catId}`
        ),
        method: "get",
      }).then(({ data }) => {
        console.log("正在修改菜单...", data.singlecategory);
        // 请求成功后从后端返回的名为data的json中把名为singlecategory的数据拿出来。
        this.category.name = data.singlecategory.name;
        this.category.catId = data.singlecategory.catId;
        this.category.icon = data.singlecategory.icon;
        this.category.productUnit = data.singlecategory.productUnit;
        // parentCid并不允许在此处修改，所以提取它并不是为了修改，而是为了优化视觉体验，在刷新时用到
        this.category.parentCid = data.singlecategory.parentCid;
      });
    },

    // 绑定"修改菜单"dialog的确定点击方法
    editCategory() {
      console.log("提交数据...", this.category);
      // 只允许提交catId、name、icon、productUnit字段
      // 先用解构表达式从category中把要提交的字段解构出来
      var { catId, name, icon, productUnit } = this.category;
      // 再建立一个新的对象Entity，只保存要提交的字段作为属性，最后也是发送这个Entity
      var Entity = {
        catId: catId,
        name: name,
        icon: icon,
        productUnit: productUnit,
      };
      this.$http({
        url: this.$http.adornUrl("/gulimallproduct/category/update"),
        method: "post",
        // 仅仅将包含catId、name、icon、productUnit的Entity发送出去
        data: this.$http.adornData(Entity, false),
      }).then(({ data }) => {
        this.$message({
          message: "修改成功",
          type: "success",
        });
        this.dialogVisibleEdit = false;
        this.getMenus();
        this.expandedKey = [this.category.parentCid];
      });
    },

    // 编写一个复原方法，每次执行完菜单操作后，我们都需要将注入对象中的全局变量category恢复为默认值，否则会对下一次操作产生错误干扰
    clear() {
      console.log("清空缓存数据...");
      this.category.catId = null;
      this.category.name = "";
      this.category.parentCid = 0;
      this.category.catLevel = 0;
      this.category.showStatus = 1;
      this.category.sort = 0;
      this.category.icon = null;
      this.category.productUnit = null;
    },
  },

  //  计算属性  类似于data 概念
  computed: {},
  //  监控data中的数据变化
  watch: {},
  //  生命周期---创建完成（可以访问当前this实例）
  created() {
    // 在created(){}中导入methods中定义的方法，那么每当这个vue被渲染时，都会自动执行created中的方法
    this.getMenus();
  },
  //  生命周期---挂载完成（可以访问DOM元素）
  mounted() {},
  beforeCreate() {}, // 生命周期---创建之前
  beforeMount() {}, // 生命周期---挂载之前
  beforeUpdate() {}, // 生命周期---更新之前
  updated() {}, // 生命周期---更新之后
  beforeDestroy() {}, // 生命周期---销毁之前
  destroyed() {}, // 生命周期---销毁完成
  activated() {}, // 如果页面有keep-alive 缓存功能，这个函数就会触发
};
</script>
<style  scoped>
</style>
